package com.guassianblur;

import android.content.Context;
import android.graphics.Bitmap;
import android.opengl.GLES20;
import android.opengl.GLUtils;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import javax.microedition.khronos.egl.EGL10;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.egl.EGLContext;
import javax.microedition.khronos.egl.EGLDisplay;
import javax.microedition.khronos.egl.EGLSurface;

public class GaussianBlurAuto {

    private static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
    private Context context;
    private int width;
    private int height;
    private int[] frameBuffers = new int[2];
    private int[] textures = new int[2];
    private int shaderProgram;

    private int aPosition;
    private int aInputTextureCoordinate;
    private int uOffsetW;
    private int uOffsetH;
    private int uBox;
    private int uHorizontal;
    private int kernelSizeLocation;
    private int kernelWeightsLocation;

    private EGLDisplay eglDisplay;
    private EGLContext eglContext;
    private EGLSurface eglSurface;
    public int kernelSize;

    public GaussianBlurAuto(Context context, int width, int height) {
        this.context = context;
        this.width = width;
        this.height = height;
        initEGL();
        initOpenGL();
    }

    private void initEGL() {
        EGL10 egl = (EGL10) EGLContext.getEGL();
        eglDisplay = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
        egl.eglInitialize(eglDisplay, new int[2]);

        int[] configurations = {
                EGL10.EGL_RENDERABLE_TYPE, 4,  // EGL_OPENGL_ES2_BIT
                EGL10.EGL_RED_SIZE, 8,
                EGL10.EGL_GREEN_SIZE, 8,
                EGL10.EGL_BLUE_SIZE, 8,
                EGL10.EGL_ALPHA_SIZE, 8,
                EGL10.EGL_DEPTH_SIZE, 16,
                EGL10.EGL_STENCIL_SIZE, 8,
                EGL10.EGL_NONE
        };

        EGLConfig[] configs = new EGLConfig[1];
        int[] numConfigs = new int[1];
        egl.eglChooseConfig(eglDisplay, configurations, configs, 1, numConfigs);
        EGLConfig eglConfig = configs[0];

        int[] contextAttribs = {
                EGL_CONTEXT_CLIENT_VERSION, 2,
                EGL10.EGL_NONE
        };

        eglContext = egl.eglCreateContext(eglDisplay, eglConfig, EGL10.EGL_NO_CONTEXT, contextAttribs);

        int[] surfaceAttribs = {
                EGL10.EGL_WIDTH, width,
                EGL10.EGL_HEIGHT, height,
                EGL10.EGL_NONE
        };

        eglSurface = egl.eglCreatePbufferSurface(eglDisplay, eglConfig, surfaceAttribs);
        egl.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext);
    }

    private void initOpenGL() {
        // Initialize OpenGL context, create framebuffer, texture, and shaders
        GLES20.glGenFramebuffers(2, frameBuffers, 0);
        GLES20.glGenTextures(2, textures, 0);

        for (int i = 0; i < 2; i++) {
            GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textures[i]);
            GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, width, height, 0, GLES20.GL_RGBA,
                    GLES20.GL_UNSIGNED_BYTE, null);
            GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
            GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);

            GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, frameBuffers[i]);
            // 这里注意到,frameBuffers[0] 是和 textures[0] 绑定的，同样 frameBuffers[1] 与 textures[1] 互相绑定
            // 下面 applyGaussianBlur 方法在渲染的时候，
            // 1. 先绑定了 frameBuffers[1] 和 textures[0],
            // 2. 然后，glsl程序将会获取到 textures[0] 中加载的图像信息进行水平方向渲染
            // 3. 渲染水平方向结束，渲染结果保存在 frameBuffers[1] 中，因为 textures[1] 在绑定了 frameBuffers[1]，
            //    因此结果渲染结果的图像对应的也在 textures[1] 中。
            // 4. 接着，绑定了 frameBuffers[0] 和 textures[1], 这样 textures[1] 就是 glsl 程序中的输入图像纹理。
            //    然后，开始渲染竖直方向。
            // 5. 渲染竖直方向结束，渲染结果保存在 frameBuffers[0] 中，
            //    因为 texture[0] 和 frameBuffers[0] 是绑定关系，因此图像纹理也对应保存在 texture[0] 中
            GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, GLES20.GL_TEXTURE_2D,
                    textures[i], 0);
        }

        shaderProgram = createShaderProgram();
    }

    private int createShaderProgram() {
        int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER,
                ShaderHelper.INSTANCE.getRawShaderResource(context, R.raw.vertex_shader_auto));
        int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER,
                ShaderHelper.INSTANCE.getRawShaderResource(context, R.raw.fragment_shader_auto));

        int program = GLES20.glCreateProgram();
        GLES20.glAttachShader(program, vertexShader);
        GLES20.glAttachShader(program, fragmentShader);
        GLES20.glLinkProgram(program);

        // 获取 attribute 位置
        aPosition = GLES20.glGetAttribLocation(program, "position");
        aInputTextureCoordinate = GLES20.glGetAttribLocation(program, "inputTextureCoordinate");
        // 获取 uniform 位置
        uOffsetW = GLES20.glGetUniformLocation(program, "wOffset");
        uOffsetH = GLES20.glGetUniformLocation(program, "hOffset");
        uHorizontal = GLES20.glGetUniformLocation(program, "horizontal");
        uBox = GLES20.glGetUniformLocation(program, "box");

        kernelSizeLocation = GLES20.glGetUniformLocation(program, "kernelSize");
        kernelWeightsLocation = GLES20.glGetUniformLocation(program, "kernelWeights");

        return program;
    }

    private void setGaussianKernelAndOffset(boolean horizontal, boolean boxBlur) {
        float offset = horizontal ? 0.5f / width : 0.5f / height;
        GLES20.glUniform1f(uOffsetW, horizontal ? offset : 0.0f);
        GLES20.glUniform1f(uOffsetH, horizontal ? 0.0f : offset);
        GLES20.glUniform1i(uHorizontal, horizontal ? 1 : 0);
        GLES20.glUniform1i(uBox, boxBlur ? 1 : 0);
        if (boxBlur) {
            //  盒状模糊
            GLES20.glUniform1f(uOffsetW, 0.5f / width);
        }
    }

    private int loadShader(int type, String shaderCode) {
        int shader = GLES20.glCreateShader(type);
        GLES20.glShaderSource(shader, shaderCode);
        GLES20.glCompileShader(shader);
        return shader;
    }

    public Bitmap applyGaussianBlur(Bitmap bitmap) {
        // Load bitmap to texture
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textures[0]);
        GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);

        // Use the shader program
        GLES20.glUseProgram(shaderProgram);

        float sigma = 99999f;
        // 计算高斯核权重
        float[] kernel = new float[kernelSize];
        float sum = 0.0f;
        int half = (kernelSize - 1) / 2;
        for (int i = 0; i < kernelSize; i++) {
            float x = i - half;
            kernel[i] = (float) Math.exp(-0.1 * (x * x) / (sigma * sigma));
            sum += kernel[i];
        }

        // 归一化权重
        for (int i = 0; i < kernelSize; i++) {
            kernel[i] /= sum;
            kernel[i] /= 1.96f;
        }
        GLES20.glUniform1fv(kernelWeightsLocation, kernel.length, kernel, 0);
        GLES20.glUniform1i(kernelSizeLocation, kernelSize);

        for (int i = 0; i < 2; i++) {
            // First pass: horizontal blur
            GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, frameBuffers[1]);
            GLES20.glViewport(0, 0, width, height);
            setGaussianKernelAndOffset(true, false);
            GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textures[0]);
            renderQuad();

            // Second pass: vertical blur
            GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, frameBuffers[0]);
            GLES20.glViewport(0, 0, width, height);
            setGaussianKernelAndOffset(false, false);
            GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textures[1]);
            renderQuad();
        }

//        // Third pass: box blur
//        GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, frameBuffers[1]);
//        GLES20.glViewport(0, 0, width, height);
//        setGaussianKernelAndOffset(true, true);
//        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textures[0]);
//        renderQuad();
//
//        GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, frameBuffers[0]);
//        GLES20.glViewport(0, 0, width, height);
//        setGaussianKernelAndOffset(false, false);
//        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textures[1]);
//        renderQuad();

        // Read the pixels from the framebuffer
        ByteBuffer buffer = ByteBuffer.allocateDirect(width * height * 4);
        GLES20.glReadPixels(0, 0, width, height, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, buffer);

        // Create a new bitmap from the buffer
        Bitmap blurredBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        buffer.rewind();
        blurredBitmap.copyPixelsFromBuffer(buffer);

        // 解绑
        GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
        return blurredBitmap;
    }

    private void renderQuad() {
        // Define vertices and texture coordinates for a full-screen quad
        float[] vertices = {
                -1.0f, -1.0f, 0.0f, 0.0f,
                1.0f, -1.0f, 1.0f, 0.0f,
                -1.0f, 1.0f, 0.0f, 1.0f,
                1.0f, 1.0f, 1.0f, 1.0f};

        FloatBuffer vertexBuffer = ByteBuffer.allocateDirect(vertices.length * 4)
                .order(ByteOrder.nativeOrder())
                .asFloatBuffer();
        vertexBuffer.put(vertices);
        vertexBuffer.position(0);

        GLES20.glEnableVertexAttribArray(aPosition);
        GLES20.glVertexAttribPointer(aPosition, 2, GLES20.GL_FLOAT, false, 4 * 4, vertexBuffer);

        vertexBuffer.position(2);
        GLES20.glEnableVertexAttribArray(aInputTextureCoordinate);
        GLES20.glVertexAttribPointer(aInputTextureCoordinate, 2, GLES20.GL_FLOAT, false, 4 * 4, vertexBuffer);

        // Draw the quad
        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);

        // Disable vertex arrays
        GLES20.glDisableVertexAttribArray(aPosition);
        GLES20.glDisableVertexAttribArray(aInputTextureCoordinate);
    }

    // Cleanup method to free resources
    public void cleanup() {
        GLES20.glDeleteFramebuffers(2, frameBuffers, 0);
        GLES20.glDeleteTextures(2, textures, 0);
        GLES20.glDeleteProgram(shaderProgram);

        EGL10 egl = (EGL10) EGLContext.getEGL();
        egl.eglDestroySurface(eglDisplay, eglSurface);
        egl.eglDestroyContext(eglDisplay, eglContext);
        egl.eglTerminate(eglDisplay);
    }
}